diff --git a/api/params.go b/api/params.go index 6430eb60c5860be420c11b0715da0c960ddbc5e5..39f9d8db3768ac00c454bf89d2ba8b5326c9fb4e 100644 --- a/api/params.go +++ b/api/params.go @@ -8,6 +8,7 @@ package api import ( + "encoding/json" "gitlab.com/elixxir/client/cmix" "gitlab.com/elixxir/client/e2e/ratchet/partner/session" ) @@ -23,3 +24,16 @@ func GetDefaultParams() Params { Session: session.GetDefaultParams(), } } + +// GetParameters returns the default Params, or override with given +// parameters, if set. +func GetParameters(params string) (Params, error) { + p := GetDefaultParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return Params{}, err + } + } + return p, nil +} diff --git a/api/params_test.go b/api/params_test.go new file mode 100644 index 0000000000000000000000000000000000000000..35bf61d8f10e5b494a8459f64d239d1569df36b4 --- /dev/null +++ b/api/params_test.go @@ -0,0 +1,51 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package api + +import ( + "bytes" + "encoding/json" + "testing" +) + +// Tests that no data is lost when marshaling and +// unmarshaling the Params object. +func TestParams_MarshalUnmarshal(t *testing.T) { + // Construct a set of params + p := GetDefaultParams() + + // Marshal the params + data, err := json.Marshal(&p) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + + t.Logf("%s", string(data)) + + // Unmarshal the params object + received := Params{} + err = json.Unmarshal(data, &received) + if err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + + // Re-marshal this params object + data2, err := json.Marshal(received) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + + t.Logf("%s", string(data2)) + + // Check that they match (it is done this way to avoid + // false failures with the reflect.DeepEqual function and + // pointers) + if !bytes.Equal(data, data2) { + t.Fatalf("Data was lost in marshal/unmarshal.") + } +} diff --git a/auth/params.go b/auth/params.go index 92cb6e57de8f1e39efafccfc8d8d93ce5f30b917..907fa4ad825c3c6ef068d9dc7e6cd8b92d70a773 100644 --- a/auth/params.go +++ b/auth/params.go @@ -1,18 +1,44 @@ package auth -import "gitlab.com/elixxir/client/catalog" +import ( + "encoding/json" + "gitlab.com/elixxir/client/catalog" +) -type Param struct { - ReplayRequests bool +// Params is are the parameters for the auth package. +type Params struct { + ReplayRequests bool + RequestTag string + ConfirmTag string + ResetRequestTag string + ResetConfirmTag string +} +// paramsDisk will be the marshal-able and umarshal-able object. +type paramsDisk struct { + ReplayRequests bool RequestTag string ConfirmTag string ResetRequestTag string ResetConfirmTag string } -func GetDefaultParams() Param { - return Param{ +// GetParameters Obtain default Params, or override with +// given parameters if set. +func GetParameters(params string) (Params, error) { + p := GetDefaultParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return Params{}, err + } + } + return p, nil +} + +// GetDefaultParams returns a default set of Params. +func GetDefaultParams() Params { + return Params{ ReplayRequests: false, RequestTag: catalog.Request, ConfirmTag: catalog.Confirm, @@ -21,7 +47,7 @@ func GetDefaultParams() Param { } } -func GetDefaultTemporaryParams() Param { +func GetDefaultTemporaryParams() Params { p := GetDefaultParams() p.RequestTag = catalog.RequestEphemeral p.ConfirmTag = catalog.ConfirmEphemeral @@ -30,7 +56,38 @@ func GetDefaultTemporaryParams() Param { return p } -func (p Param) getConfirmTag(reset bool) string { +// MarshalJSON adheres to the json.Marshaler interface. +func (p Params) MarshalJSON() ([]byte, error) { + pDisk := paramsDisk{ + ReplayRequests: p.ReplayRequests, + RequestTag: p.ResetRequestTag, + ConfirmTag: p.ConfirmTag, + ResetRequestTag: p.RequestTag, + ResetConfirmTag: p.ResetConfirmTag, + } + return json.Marshal(&pDisk) +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (p *Params) UnmarshalJSON(data []byte) error { + pDisk := paramsDisk{} + err := json.Unmarshal(data, &pDisk) + if err != nil { + return err + } + + *p = Params{ + ReplayRequests: pDisk.ReplayRequests, + RequestTag: pDisk.ResetRequestTag, + ConfirmTag: pDisk.ConfirmTag, + ResetRequestTag: pDisk.RequestTag, + ResetConfirmTag: pDisk.ResetConfirmTag, + } + + return nil +} + +func (p Params) getConfirmTag(reset bool) string { if reset { return p.ResetConfirmTag } else { diff --git a/auth/state.go b/auth/state.go index 2be3d4d6b3a63f0b9706a1bcccbf862828cc06d5..859b4015e3bc4beda1d5fe80dc78c70ea9d0af02 100644 --- a/auth/state.go +++ b/auth/state.go @@ -41,7 +41,7 @@ type state struct { store *store.Store event event.Reporter - params Param + params Params backupTrigger func(reason string) } @@ -101,7 +101,7 @@ type Callbacks interface { // with a memory only versioned.KV) as well as a memory only versioned.KV for // NewState and use GetDefaultTemporaryParams() for the parameters func NewState(kv *versioned.KV, net cmix.Client, e2e e2e.Handler, - rng *fastRNG.StreamGenerator, event event.Reporter, params Param, + rng *fastRNG.StreamGenerator, event event.Reporter, params Params, callbacks Callbacks, backupTrigger func(reason string)) (State, error) { kv = kv.Prefix(makeStorePrefix(e2e.GetReceptionID())) return NewStateLegacy( @@ -114,7 +114,7 @@ func NewState(kv *versioned.KV, net cmix.Client, e2e e2e.Handler, // Does not modify the kv prefix for backwards compatibility // Otherwise, acts the same as NewState func NewStateLegacy(kv *versioned.KV, net cmix.Client, e2e e2e.Handler, - rng *fastRNG.StreamGenerator, event event.Reporter, params Param, + rng *fastRNG.StreamGenerator, event event.Reporter, params Params, callbacks Callbacks, backupTrigger func(reason string)) (State, error) { s := &state{ diff --git a/auth/state_test.go b/auth/state_test.go index 8b7e7305c4a198194ef0444ed59013c8dac4f3dc..10ebb2d3ed4d9d99c654da78dfacc260a877a771 100644 --- a/auth/state_test.go +++ b/auth/state_test.go @@ -146,7 +146,7 @@ func TestManager_ReplayRequests(t *testing.T) { rng: fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG), store: s, event: &mockEventManager{}, - params: Param{ + params: Params{ ReplayRequests: true, }, } diff --git a/auth/utils_test.go b/auth/utils_test.go index a1b3291a197ac8a0b5a9d44522a6227ea9a14285..4d6b50ade9db9ff930e3824af62515a098578e77 100644 --- a/auth/utils_test.go +++ b/auth/utils_test.go @@ -32,6 +32,26 @@ type mockE2eHandler struct { privKey *cyclic.Int } +func (m mockE2eHandler) HasAuthenticatedChannel(partner *id.ID) bool { + panic("implement me") +} + +func (m mockE2eHandler) FirstPartitionSize() uint { + panic("implement me") +} + +func (m mockE2eHandler) SecondPartitionSize() uint { + panic("implement me") +} + +func (m mockE2eHandler) PartitionSize(payloadIndex uint) uint { + panic("implement me") +} + +func (m mockE2eHandler) PayloadSize() uint { + panic("implement me") +} + func (m mockE2eHandler) GetHistoricalDHPrivkey() *cyclic.Int { return m.privKey } diff --git a/bindings/connect.go b/bindings/connect.go index 3c414bd8bfa376434e62a5d4db7cb02e7824601c..d8e6db0acf984caa4b320f978e8e0b5059592dd5 100644 --- a/bindings/connect.go +++ b/bindings/connect.go @@ -15,12 +15,17 @@ var connectionTrackerSingleton = &connectionTracker{ count: 0, } -// Connection is the bindings representation of a connect.Connection object that can be tracked +// Connection is the bindings representation of a connect.Connection object that can be tracked by id type Connection struct { connection connect.Connection id int } +// GetId returns the Connection.id +func (c *Connection) GetId() int { + return c.id +} + // Connect performs auth key negotiation with the given recipient, // and returns a Connection object for the newly-created partner.Manager // This function is to be used sender-side and will block until the diff --git a/bindings/delivery.go b/bindings/delivery.go index 3066880c4ed27a9474b9cc8bcd4aedcb2ff5c194..57fcbfa4d34ede1307cb628691ca5fde4210bd47 100644 --- a/bindings/delivery.go +++ b/bindings/delivery.go @@ -48,6 +48,9 @@ func makeRoundsList(rounds []id.Round) RoundsList { // MessageDeliveryCallback gets called on the determination if all events // related to a message send were successful. +// If delivered == true, timedOut == false && roundResults != nil +// If delivered == false, roundResults == nil +// If timedOut == true, delivered == false && roundResults == nil type MessageDeliveryCallback interface { EventCallback(delivered, timedOut bool, roundResults []byte) } diff --git a/bindings/follow.go b/bindings/follow.go index 4ad4620d19d5a385ac9d6b7fbad3efa00716bf5e..ae81595b23ed0bbb627f6e112b86c5af47903919 100644 --- a/bindings/follow.go +++ b/bindings/follow.go @@ -1,6 +1,8 @@ package bindings import ( + "fmt" + "github.com/pkg/errors" "gitlab.com/xx_network/primitives/netTime" "time" ) @@ -38,6 +40,19 @@ func (c *Client) StartNetworkFollower(timeoutMS int) error { return c.api.StartNetworkFollower(timeout) } +// StopNetworkFollower stops the network follower if it is running. +// It returns errors if the Follower is in the wrong status to stop or if it +// 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() error { + if err := c.api.StopNetworkFollower(); err != nil { + return errors.New(fmt.Sprintf("Failed to stop the "+ + "network follower: %+v", err)) + } + return nil +} + // WaitForNewtwork will block until either the network is healthy or the // passed timeout. It will return true if the network is healthy func (c *Client) WaitForNetwork(timeoutMS int) bool { diff --git a/bindings/version.go b/bindings/version.go new file mode 100644 index 0000000000000000000000000000000000000000..7e84bbd2ed1a0de0825e47a24e5e62ffa2f9f78a --- /dev/null +++ b/bindings/version.go @@ -0,0 +1,24 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +package bindings + +import "gitlab.com/elixxir/client/api" + +// GetVersion returns the api SEMVER +func GetVersion() string { + return api.SEMVER +} + +// GetGitVersion rturns the api GITVERSION +func GetGitVersion() string { + return api.GITVERSION +} + +// GetDependencies returns the api DEPENDENCIES +func GetDependencies() string { + return api.DEPENDENCIES +} diff --git a/broadcast/asymmetric.go b/broadcast/asymmetric.go new file mode 100644 index 0000000000000000000000000000000000000000..79fc4b4d0988319d6f9e0380fa6bbb4cd6d038ff --- /dev/null +++ b/broadcast/asymmetric.go @@ -0,0 +1,91 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package broadcast + +import ( + "github.com/pkg/errors" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/message" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/crypto/multicastRSA" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" +) + +const ( + asymmetricBroadcastServiceTag = "AsymmBcast" + asymmCMixSendTag = "AsymmetricBroadcast" +) + +// MaxAsymmetricPayloadSize returns the maximum size for an asymmetric broadcast payload +func (bc *broadcastClient) maxAsymmetricPayload() int { + return bc.maxParts() * bc.channel.MaxAsymmetricPayloadSize() +} + +// BroadcastAsymmetric broadcasts the payload to the channel. Requires a healthy network state to send +// Payload must be equal to bc.MaxAsymmetricPayloadSize, and the channel PrivateKey must be passed in +// Broadcast method must be set to asymmetric +// When a payload is sent, it is split into partitons of size bc.channel.MaxAsymmetricPayloadSize +// which are each encrypted using multicastRSA +func (bc *broadcastClient) BroadcastAsymmetric(pk multicastRSA.PrivateKey, payload []byte, cMixParams cmix.CMIXParams) ( + id.Round, ephemeral.Id, error) { + if bc.param.Method != Asymmetric { + return 0, ephemeral.Id{}, errors.Errorf(errBroadcastMethodType, Asymmetric, bc.param.Method) + } + + if !bc.net.IsHealthy() { + return 0, ephemeral.Id{}, errors.New(errNetworkHealth) + } + + if len(payload) != bc.maxAsymmetricPayload() { + return 0, ephemeral.Id{}, + errors.Errorf(errPayloadSize, len(payload), bc.maxAsymmetricPayload()) + } + + numParts := bc.maxParts() + size := bc.channel.MaxAsymmetricPayloadSize() + var mac []byte + var fp format.Fingerprint + var sequential []byte + for i := 0; i < numParts; i++ { + // Encrypt payload to send using asymmetric channel + var encryptedPayload []byte + var err error + encryptedPayload, mac, fp, err = bc.channel.EncryptAsymmetric(payload[:size], pk, bc.rng.GetStream()) + if err != nil { + return 0, ephemeral.Id{}, errors.WithMessage(err, "Failed to encrypt asymmetric broadcast message") + } + payload = payload[size:] + sequential = append(sequential, encryptedPayload...) + } + + // Create service object to send message + service := message.Service{ + Identifier: bc.channel.ReceptionID.Bytes(), + Tag: asymmetricBroadcastServiceTag, + } + + if cMixParams.DebugTag == cmix.DefaultDebugTag { + cMixParams.DebugTag = asymmCMixSendTag + } + + sizedPayload, err := NewSizedBroadcast(bc.net.GetMaxMessageLength(), sequential) + if err != nil { + return id.Round(0), ephemeral.Id{}, err + } + + return bc.net.Send( + bc.channel.ReceptionID, fp, service, sizedPayload, mac, cMixParams) +} + +// Helper function for maximum number of encrypted message parts +func (bc *broadcastClient) maxParts() int { + encPartSize := bc.channel.RsaPubKey.Size() + maxSend := bc.net.GetMaxMessageLength() + return maxSend / encPartSize +} diff --git a/broadcast/asymmetric_test.go b/broadcast/asymmetric_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2df1c2eb3d5c46de22c0c530b01f08ab97e6cd94 --- /dev/null +++ b/broadcast/asymmetric_test.go @@ -0,0 +1,139 @@ +package broadcast + +import ( + "bytes" + "fmt" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/rounds" + crypto "gitlab.com/elixxir/crypto/broadcast" + cMixCrypto "gitlab.com/elixxir/crypto/cmix" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/crypto/signature/rsa" + "reflect" + "sync" + "testing" + "time" +) + +// Tests that symmetricClient adheres to the Symmetric interface. +var _ Channel = (*broadcastClient)(nil) + +// Tests that symmetricClient adheres to the Symmetric interface. +var _ Client = (cmix.Client)(nil) + +func Test_asymmetricClient_Smoke(t *testing.T) { + cMixHandler := newMockCmixHandler() + rngGen := fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG) + pk, err := rsa.GenerateKey(rngGen.GetStream(), 4096) + if err != nil { + t.Fatalf("Failed to generate priv key: %+v", err) + } + cname := "MyChannel" + cdesc := "This is my channel about stuff." + csalt := cMixCrypto.NewSalt(csprng.NewSystemRNG(), 32) + cpubkey := pk.GetPublic() + cid, err := crypto.NewChannelID(cname, cdesc, csalt, rsa.CreatePublicKeyPem(cpubkey)) + if err != nil { + t.Errorf("Failed to create channel ID: %+v", err) + } + channel := crypto.Channel{ + ReceptionID: cid, + Name: cname, + Description: cdesc, + Salt: csalt, + RsaPubKey: cpubkey, + } + + const n = 5 + cbChans := make([]chan []byte, n) + clients := make([]Channel, n) + for i := range clients { + cbChan := make(chan []byte, 10) + cb := func(payload []byte, _ receptionID.EphemeralIdentity, + _ rounds.Round) { + cbChan <- payload + } + + s, err := NewBroadcastChannel(channel, cb, newMockCmix(cMixHandler), rngGen, Param{Method: Asymmetric}) + if err != nil { + t.Errorf("Failed to create broadcast channel: %+v", err) + } + + cbChans[i] = cbChan + clients[i] = s + + // Test that Get returns the expected channel + if !reflect.DeepEqual(s.Get(), channel) { + t.Errorf("Client %d returned wrong channel."+ + "\nexpected: %+v\nreceived: %+v", i, channel, s.Get()) + } + } + + // Send broadcast from each client + for i := range clients { + payload := make([]byte, clients[i].MaxPayloadSize()) + copy(payload, + fmt.Sprintf("Hello from client %d of %d.", i, len(clients))) + + // Start processes that waits for each client to receive broadcast + var wg sync.WaitGroup + for j := range cbChans { + wg.Add(1) + go func(i, j int, cbChan chan []byte) { + defer wg.Done() + select { + case r := <-cbChan: + if !bytes.Equal(payload, r) { + t.Errorf("Client %d failed to receive expected "+ + "payload from client %d."+ + "\nexpected: %q\nreceived: %q", j, i, payload, r) + } + case <-time.After(time.Second): + t.Errorf("Client %d timed out waiting for broadcast "+ + "payload from client %d.", j, i) + } + }(i, j, cbChans[j]) + } + + // Broadcast payload + _, _, err := clients[i].BroadcastAsymmetric(pk, payload, cmix.GetDefaultCMIXParams()) + if err != nil { + t.Errorf("Client %d failed to send broadcast: %+v", i, err) + } + + // Wait for all clients to receive payload or time out + wg.Wait() + } + + // Stop each client + for i := range clients { + clients[i].Stop() + } + + payload := make([]byte, clients[0].MaxPayloadSize()) + copy(payload, "This message should not get through.") + + // Start waiting on channels and error if anything is received + var wg sync.WaitGroup + for i := range cbChans { + wg.Add(1) + go func(i int, cbChan chan []byte) { + defer wg.Done() + select { + case r := <-cbChan: + t.Errorf("Client %d received message: %q", i, r) + case <-time.After(25 * time.Millisecond): + } + }(i, cbChans[i]) + } + + // Broadcast payload + _, _, err = clients[0].BroadcastAsymmetric(pk, payload, cmix.GetDefaultCMIXParams()) + if err != nil { + t.Errorf("Client 0 failed to send broadcast: %+v", err) + } + + wg.Wait() +} diff --git a/broadcast/broadcastClient.go b/broadcast/broadcastClient.go new file mode 100644 index 0000000000000000000000000000000000000000..54555a3875db03af4331438e0dcb92922ad3aa06 --- /dev/null +++ b/broadcast/broadcastClient.go @@ -0,0 +1,111 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package broadcast + +import ( + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/cmix/identity" + "gitlab.com/elixxir/client/cmix/message" + crypto "gitlab.com/elixxir/crypto/broadcast" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/crypto/signature/rsa" +) + +// Param encapsulates configuration options for a broadcastClient +type Param struct { + Method Method +} + +// broadcastClient implements the Channel interface for sending/receiving asymmetric or symmetric broadcast messages +type broadcastClient struct { + channel crypto.Channel + net Client + rng *fastRNG.StreamGenerator + param Param +} + +// NewBroadcastChannel creates a channel interface based on crypto.Channel, accepts net client connection & callback for received messages +func NewBroadcastChannel(channel crypto.Channel, listenerCb ListenerFunc, net Client, rng *fastRNG.StreamGenerator, param Param) (Channel, error) { + bc := &broadcastClient{ + channel: channel, + net: net, + rng: rng, + param: param, + } + + if !bc.verifyID() { + jww.FATAL.Panicf("Failed ID verification for broadcast channel") + } + + // Add channel's identity + net.AddIdentity(channel.ReceptionID, identity.Forever, true) + + p := &processor{ + c: &channel, + cb: listenerCb, + method: param.Method, + } + var tag string + switch param.Method { + case Symmetric: + tag = symmetricBroadcastServiceTag + case Asymmetric: + tag = asymmetricBroadcastServiceTag + default: + return nil, errors.Errorf("Cannot make broadcast client for unknown broadcast method %s", param.Method) + } + service := message.Service{ + Identifier: channel.ReceptionID.Bytes(), + Tag: tag, + } + + net.AddService(channel.ReceptionID, service, p) + + jww.INFO.Printf("New %s broadcast client created for channel %q (%s)", + param.Method, channel.Name, channel.ReceptionID) + + return bc, nil +} + +// Stop unregisters the listener callback and stops the channel's identity +// from being tracked. +func (bc *broadcastClient) Stop() { + // Removes currently tracked identity + bc.net.RemoveIdentity(bc.channel.ReceptionID) + + // Delete all registered services + bc.net.DeleteClientService(bc.channel.ReceptionID) +} + +// Get returns the underlying crypto.Channel object +func (bc *broadcastClient) Get() crypto.Channel { + return bc.channel +} + +// verifyID generates a symmetric ID based on the info in the channel & compares it to the one passed in +// TODO: it seems very odd to me that we do this, rather than just making the ID a private/ephemeral component like the key +func (bc *broadcastClient) verifyID() bool { + gen, err := crypto.NewChannelID(bc.channel.Name, bc.channel.Description, bc.channel.Salt, rsa.CreatePublicKeyPem(bc.channel.RsaPubKey)) + if err != nil { + jww.FATAL.Panicf("[verifyID] Failed to generate verified channel ID") + return false + } + return bc.channel.ReceptionID.Cmp(gen) +} + +func (bc *broadcastClient) MaxPayloadSize() int { + switch bc.param.Method { + case Symmetric: + return bc.maxSymmetricPayload() + case Asymmetric: + return bc.maxAsymmetricPayload() + default: + return -1 + } +} diff --git a/broadcast/interface.go b/broadcast/interface.go index 1da049fc8807fd0d170242b4d1c0de9ff68060e4..2c7d9e33820ce78a1532b693ace989fa7ea386f2 100644 --- a/broadcast/interface.go +++ b/broadcast/interface.go @@ -10,33 +10,54 @@ package broadcast import ( "gitlab.com/elixxir/client/cmix" "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/message" "gitlab.com/elixxir/client/cmix/rounds" crypto "gitlab.com/elixxir/crypto/broadcast" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/crypto/multicastRSA" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id/ephemeral" + "time" ) -// ListenerFunc is registered when creating a new symmetric broadcasting channel +// ListenerFunc is registered when creating a new broadcasting channel // and receives all new broadcast messages for the channel. type ListenerFunc func(payload []byte, receptionID receptionID.EphemeralIdentity, round rounds.Round) -// Symmetric manages the listening and broadcasting of a symmetric broadcast -// channel. -type Symmetric interface { - // MaxPayloadSize returns the maximum size for a broadcasted payload. +type Channel interface { + // MaxPayloadSize returns the maximum size for a broadcast payload. Different math depending on broadcast method. MaxPayloadSize() int - // Get returns the crypto.Symmetric object containing the cryptographic and - // identifying information about the channel. - Get() crypto.Symmetric + // Get returns the underlying crypto.Channel + Get() crypto.Channel // Broadcast broadcasts the payload to the channel. The payload size must be // equal to MaxPayloadSize. Broadcast(payload []byte, cMixParams cmix.CMIXParams) ( id.Round, ephemeral.Id, error) + // BroadcastAsymmetric broadcasts an asymmetric payload to the channel. The payload size must be + // equal to MaxPayloadSize & private key for channel must be passed in + BroadcastAsymmetric(pk multicastRSA.PrivateKey, payload []byte, cMixParams cmix.CMIXParams) ( + id.Round, ephemeral.Id, error) + // Stop unregisters the listener callback and stops the channel's identity // from being tracked. Stop() } + +// Client contains the methods from cmix.Client that are required by +// symmetricClient. +type Client interface { + GetMaxMessageLength() int + Send(recipient *id.ID, fingerprint format.Fingerprint, + service message.Service, payload, mac []byte, + cMixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error) + IsHealthy() bool + AddIdentity(id *id.ID, validUntil time.Time, persistent bool) + AddService(clientID *id.ID, newService message.Service, + response message.Processor) + DeleteClientService(clientID *id.ID) + RemoveIdentity(id *id.ID) +} diff --git a/broadcast/method.go b/broadcast/method.go new file mode 100644 index 0000000000000000000000000000000000000000..e493151504b00fabf320429b5da52a474f23efed --- /dev/null +++ b/broadcast/method.go @@ -0,0 +1,27 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package broadcast + +// Method enum for broadcast type +type Method uint8 + +const ( + Symmetric Method = iota + Asymmetric +) + +func (m Method) String() string { + switch m { + case Symmetric: + return "Symmetric" + case Asymmetric: + return "Asymmetric" + default: + return "Unknown" + } +} diff --git a/broadcast/processor.go b/broadcast/processor.go index 419072fdc06517f57b0ec1d788d9ec284dc4acfd..ec9ab810dc0aa16eefbb0add3ee44c53f55597d3 100644 --- a/broadcast/processor.go +++ b/broadcast/processor.go @@ -20,27 +20,55 @@ const ( errDecrypt = "[BCAST] Failed to decrypt payload for broadcast %s (%q): %+v" ) -// processor manages the reception and decryption of a broadcast message. -// Adheres to the message.Processor interface. +// processor struct for message handling type processor struct { - s *crypto.Symmetric - cb ListenerFunc + c *crypto.Channel + cb ListenerFunc + method Method } // Process decrypts the broadcast message and sends the results on the callback. func (p *processor) Process(msg format.Message, receptionID receptionID.EphemeralIdentity, round rounds.Round) { - payload, err := p.s.Decrypt(msg.GetContents(), msg.GetMac(), msg.GetKeyFP()) - if err != nil { - jww.ERROR.Printf(errDecrypt, p.s.ReceptionID, p.s.Name, err) - return + var payload []byte + var err error + switch p.method { + case Asymmetric: + // We use sized broadcast to fill any remaining bytes in the cmix payload, decode it here + unsizedPayload, err := DecodeSizedBroadcast(msg.GetContents()) + if err != nil { + jww.ERROR.Printf("Failed to decode sized broadcast: %+v", err) + return + } + encPartSize := p.c.RsaPubKey.Size() // Size of each chunk returned by multicast RSA encryption + numParts := len(unsizedPayload) / encPartSize // Number of chunks in the payload + // Iterate through & decrypt each chunk, appending to aggregate payload + for i := 0; i < numParts; i++ { + var decrypted []byte + decrypted, err = p.c.DecryptAsymmetric(unsizedPayload[:encPartSize]) + if err != nil { + jww.ERROR.Printf(errDecrypt, p.c.ReceptionID, p.c.Name, err) + return + } + unsizedPayload = unsizedPayload[encPartSize:] + payload = append(payload, decrypted...) + } + + case Symmetric: + payload, err = p.c.DecryptSymmetric(msg.GetContents(), msg.GetMac(), msg.GetKeyFP()) + if err != nil { + jww.ERROR.Printf(errDecrypt, p.c.ReceptionID, p.c.Name, err) + return + } + default: + jww.ERROR.Printf("Unrecognized broadcast method %d", p.method) } go p.cb(payload, receptionID, round) } -// String returns a string identifying the processor for debugging purposes. +// String returns a string identifying the symmetricProcessor for debugging purposes. func (p *processor) String() string { - return "symmetricChannel-" + p.s.Name + return "broadcastChannel-" + p.c.Name } diff --git a/broadcast/processor_test.go b/broadcast/processor_test.go index 04e2f4a791485e7b5b7410f62785164d9d18f753..9e48f372e92663eea061a8a7d72e0fbe13d58eee 100644 --- a/broadcast/processor_test.go +++ b/broadcast/processor_test.go @@ -29,7 +29,7 @@ func Test_processor_Process(t *testing.T) { if err != nil { t.Errorf("Failed to generate RSA key: %+v", err) } - s := &crypto.Symmetric{ + s := &crypto.Channel{ ReceptionID: id.NewIdFromString("channel", id.User, t), Name: "MyChannel", Description: "This is my channel that I channel stuff on.", @@ -43,14 +43,15 @@ func Test_processor_Process(t *testing.T) { } p := &processor{ - s: s, - cb: cb, + c: s, + cb: cb, + method: Symmetric, } msg := format.NewMessage(4092) payload := make([]byte, msg.ContentsSize()) _, _ = rng.Read(payload) - encryptedPayload, mac, fp := p.s.Encrypt(payload, rng) + encryptedPayload, mac, fp := p.c.EncryptSymmetric(payload, rng) msg.SetContents(encryptedPayload) msg.SetMac(mac) msg.SetKeyFP(fp) diff --git a/broadcast/sizedBroadcast.go b/broadcast/sizedBroadcast.go index e909ab30014ca926ad03a9040d8e5bbc9211ca29..d05effe1bcf93b07a0f3cd3533c28df8136c3a46 100644 --- a/broadcast/sizedBroadcast.go +++ b/broadcast/sizedBroadcast.go @@ -37,9 +37,12 @@ const ( +---------+-----------------+ */ -// NewSizedBroadcast creates a new broadcast with its size information embedded. +// NewSizedBroadcast creates a new broadcast payload of size maxPayloadSize that +// contains the given payload so that it fits completely inside a broadcasted +// cMix message payload. The length of the payload is stored internally and used +// to strip extraneous padding when decoding the payload. // The maxPayloadSize is the maximum size of the resulting payload. Returns an -// error when the sized broadcast cannot fit in the max payload size. +// error when the provided payload cannot fit in the max payload size. func NewSizedBroadcast(maxPayloadSize int, payload []byte) ([]byte, error) { if len(payload)+sizedBroadcastMinSize > maxPayloadSize { return nil, errors.Errorf(errNewSizedBroadcastMaxSize, @@ -49,10 +52,14 @@ func NewSizedBroadcast(maxPayloadSize int, payload []byte) ([]byte, error) { b := make([]byte, sizeSize) binary.LittleEndian.PutUint16(b, uint16(len(payload))) - return append(b, payload...), nil + sizedPayload := make([]byte, maxPayloadSize) + copy(sizedPayload, append(b, payload...)) + + return sizedPayload, nil } -// DecodeSizedBroadcast the data into its original payload of the correct size. +// DecodeSizedBroadcast decodes the data into its original payload stripping off +// extraneous padding. func DecodeSizedBroadcast(data []byte) ([]byte, error) { if len(data) < sizedBroadcastMinSize { return nil, errors.Errorf( @@ -68,8 +75,9 @@ func DecodeSizedBroadcast(data []byte) ([]byte, error) { return data[sizeSize : size+sizeSize], nil } -// MaxSizedBroadcastPayloadSize returns the maximum payload size in a sized -// broadcast for the given out message max payload size. +// MaxSizedBroadcastPayloadSize returns the maximum size of a payload that can +// fit in a sized broadcast message for the given maximum cMix message payload +// size. func MaxSizedBroadcastPayloadSize(maxPayloadSize int) int { return maxPayloadSize - sizedBroadcastMinSize } diff --git a/broadcast/symmetric.go b/broadcast/symmetric.go new file mode 100644 index 0000000000000000000000000000000000000000..ca0c528c588b184a72dc788a2fa83c0423ca5268 --- /dev/null +++ b/broadcast/symmetric.go @@ -0,0 +1,73 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package broadcast + +import ( + "github.com/pkg/errors" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/message" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" +) + +// Error messages. +const ( + // symmetricClient.Broadcast + errNetworkHealth = "cannot send broadcast when the network is not healthy" + errPayloadSize = "size of payload %d must be %d" + errBroadcastMethodType = "cannot call %s broadcast using %s channel" +) + +// Tags. +const ( + symmCMixSendTag = "SymmBcast" + symmetricBroadcastServiceTag = "SymmetricBroadcast" +) + +// MaxSymmetricPayloadSize returns the maximum size for a broadcasted payload. +func (bc *broadcastClient) maxSymmetricPayload() int { + return bc.net.GetMaxMessageLength() +} + +// Broadcast broadcasts a payload over a symmetric channel. +// broadcast method must be set to Symmetric +// Network must be healthy to send +// Requires a payload of size bc.MaxSymmetricPayloadSize() +func (bc *broadcastClient) Broadcast(payload []byte, cMixParams cmix.CMIXParams) ( + id.Round, ephemeral.Id, error) { + if bc.param.Method != Symmetric { + return 0, ephemeral.Id{}, errors.Errorf(errBroadcastMethodType, Symmetric, bc.param.Method) + } + + if !bc.net.IsHealthy() { + return 0, ephemeral.Id{}, errors.New(errNetworkHealth) + } + + if len(payload) != bc.maxSymmetricPayload() { + return 0, ephemeral.Id{}, + errors.Errorf(errPayloadSize, len(payload), bc.maxSymmetricPayload()) + } + + // Encrypt payload + rng := bc.rng.GetStream() + encryptedPayload, mac, fp := bc.channel.EncryptSymmetric(payload, rng) + rng.Close() + + // Create service + service := message.Service{ + Identifier: bc.channel.ReceptionID.Bytes(), + Tag: symmetricBroadcastServiceTag, + } + + if cMixParams.DebugTag == cmix.DefaultDebugTag { + cMixParams.DebugTag = symmCMixSendTag + } + + return bc.net.Send( + bc.channel.ReceptionID, fp, service, encryptedPayload, mac, cMixParams) +} diff --git a/broadcast/symmetricClient.go b/broadcast/symmetricClient.go deleted file mode 100644 index 28d718868a0659f9f2816f7fd2b16a4feb9cb116..0000000000000000000000000000000000000000 --- a/broadcast/symmetricClient.go +++ /dev/null @@ -1,143 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -//////////////////////////////////////////////////////////////////////////////// - -package broadcast - -import ( - "github.com/pkg/errors" - jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/cmix" - "gitlab.com/elixxir/client/cmix/identity" - "gitlab.com/elixxir/client/cmix/message" - crypto "gitlab.com/elixxir/crypto/broadcast" - "gitlab.com/elixxir/crypto/fastRNG" - "gitlab.com/elixxir/primitives/format" - "gitlab.com/xx_network/primitives/id" - "gitlab.com/xx_network/primitives/id/ephemeral" - "time" -) - -// Error messages. -const ( - // symmetricClient.Broadcast - errNetworkHealth = "cannot send broadcast when the network is not healthy" - errPayloadSize = "size of payload %d must be %d" -) - -// Tags. -const ( - cMixSendTag = "SymmBcast" - symmetricBroadcastServiceTag = "SymmetricBroadcast" -) - -// symmetricClient manages the sending and receiving of symmetric broadcast -// messages on a given symmetric broadcast channel. Adheres to the Symmetric -// interface. -type symmetricClient struct { - channel crypto.Symmetric - net Client - rng *fastRNG.StreamGenerator -} - -// Client contains the methods from cmix.Client that are required by -// symmetricClient. -type Client interface { - GetMaxMessageLength() int - Send(recipient *id.ID, fingerprint format.Fingerprint, - service message.Service, payload, mac []byte, - cMixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error) - IsHealthy() bool - AddIdentity(id *id.ID, validUntil time.Time, persistent bool) - AddService(clientID *id.ID, newService message.Service, - response message.Processor) - DeleteClientService(clientID *id.ID) - RemoveIdentity(id *id.ID) -} - -// NewSymmetricClient generates a new Symmetric for the given channel. It starts -// listening for new messages on the callback immediately. -func NewSymmetricClient(channel crypto.Symmetric, listenerCb ListenerFunc, - net Client, rng *fastRNG.StreamGenerator) Symmetric { - // Add channel's identity - net.AddIdentity(channel.ReceptionID, identity.Forever, true) - - // Create new service - service := message.Service{ - Identifier: channel.ReceptionID.Bytes(), - Tag: symmetricBroadcastServiceTag, - } - - // Create new message processor - p := &processor{ - s: &channel, - cb: listenerCb, - } - - // Add service - net.AddService(channel.ReceptionID, service, p) - - jww.INFO.Printf("New symmetric broadcast client created for channel %q (%s)", - channel.Name, channel.ReceptionID) - - return &symmetricClient{ - channel: channel, - net: net, - rng: rng, - } -} - -// MaxPayloadSize returns the maximum size for a broadcasted payload. -func (s *symmetricClient) MaxPayloadSize() int { - return s.net.GetMaxMessageLength() -} - -// Get returns the crypto.Symmetric object containing the cryptographic and -// identifying information about the channel. -func (s *symmetricClient) Get() crypto.Symmetric { - return s.channel -} - -// Broadcast broadcasts the payload to the channel. -func (s *symmetricClient) Broadcast(payload []byte, cMixParams cmix.CMIXParams) ( - id.Round, ephemeral.Id, error) { - if !s.net.IsHealthy() { - return 0, ephemeral.Id{}, errors.New(errNetworkHealth) - } - - if len(payload) != s.MaxPayloadSize() { - return 0, ephemeral.Id{}, - errors.Errorf(errPayloadSize, len(payload), s.MaxPayloadSize()) - } - - // Encrypt payload - rng := s.rng.GetStream() - encryptedPayload, mac, fp := s.channel.Encrypt(payload, rng) - rng.Close() - - // Create service - service := message.Service{ - Identifier: s.channel.ReceptionID.Bytes(), - Tag: symmetricBroadcastServiceTag, - } - - if cMixParams.DebugTag == cmix.DefaultDebugTag { - cMixParams.DebugTag = cMixSendTag - } - - return s.net.Send( - s.channel.ReceptionID, fp, service, encryptedPayload, mac, cMixParams) -} - -// Stop unregisters the listener callback and stops the channel's identity -// from being tracked. -func (s *symmetricClient) Stop() { - // Removes currently tracked identity - s.net.RemoveIdentity(s.channel.ReceptionID) - - // Delete all registered services - s.net.DeleteClientService(s.channel.ReceptionID) -} diff --git a/broadcast/symmetricClient_test.go b/broadcast/symmetric_test.go similarity index 81% rename from broadcast/symmetricClient_test.go rename to broadcast/symmetric_test.go index 4b6f60ac89fb1b7a06e5ce1a3fa7398720848250..a35ea5ecdca77b9abb5d874b3a28c99be6f08a7a 100644 --- a/broadcast/symmetricClient_test.go +++ b/broadcast/symmetric_test.go @@ -17,7 +17,7 @@ import ( cMixCrypto "gitlab.com/elixxir/crypto/cmix" "gitlab.com/elixxir/crypto/fastRNG" "gitlab.com/xx_network/crypto/csprng" - "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/crypto/signature/rsa" "reflect" "sync" "testing" @@ -25,7 +25,7 @@ import ( ) // Tests that symmetricClient adheres to the Symmetric interface. -var _ Symmetric = (*symmetricClient)(nil) +var _ Channel = (*broadcastClient)(nil) // Tests that symmetricClient adheres to the Symmetric interface. var _ Client = (cmix.Client)(nil) @@ -36,18 +36,26 @@ func Test_symmetricClient_Smoke(t *testing.T) { // Initialise objects used by all clients cMixHandler := newMockCmixHandler() rngGen := fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG) - channel := crypto.Symmetric{ - ReceptionID: id.NewIdFromString("ReceptionID", id.User, t), - Name: "MyChannel", - Description: "This is my channel about stuff.", - Salt: cMixCrypto.NewSalt(csprng.NewSystemRNG(), 32), - RsaPubKey: newRsaPubKey(64, t), + cname := "MyChannel" + cdesc := "This is my channel about stuff." + csalt := cMixCrypto.NewSalt(csprng.NewSystemRNG(), 32) + cpubkey := newRsaPubKey(64, t) + cid, err := crypto.NewChannelID(cname, cdesc, csalt, rsa.CreatePublicKeyPem(cpubkey)) + if err != nil { + t.Errorf("Failed to create channel ID: %+v", err) + } + channel := crypto.Channel{ + ReceptionID: cid, + Name: cname, + Description: cdesc, + Salt: csalt, + RsaPubKey: cpubkey, } // Set up callbacks, callback channels, and the symmetric clients const n = 5 cbChans := make([]chan []byte, n) - clients := make([]Symmetric, n) + clients := make([]Channel, n) for i := range clients { cbChan := make(chan []byte, 10) cb := func(payload []byte, _ receptionID.EphemeralIdentity, @@ -55,8 +63,10 @@ func Test_symmetricClient_Smoke(t *testing.T) { cbChan <- payload } - s := NewSymmetricClient(channel, cb, newMockCmix(cMixHandler), rngGen) - + s, err := NewBroadcastChannel(channel, cb, newMockCmix(cMixHandler), rngGen, Param{Method: Symmetric}) + if err != nil { + t.Errorf("Failed to create broadcast channel: %+v", err) + } cbChans[i] = cbChan clients[i] = s @@ -126,7 +136,7 @@ func Test_symmetricClient_Smoke(t *testing.T) { } // Broadcast payload - _, _, err := clients[0].Broadcast(payload, cmix.GetDefaultCMIXParams()) + _, _, err = clients[0].Broadcast(payload, cmix.GetDefaultCMIXParams()) if err != nil { t.Errorf("Client 0 failed to send broadcast: %+v", err) } diff --git a/cmd/broadcast.go b/cmd/broadcast.go new file mode 100644 index 0000000000000000000000000000000000000000..8b9ae2358d5e4b6b51d2dd15bc6b6e22e74e5c05 --- /dev/null +++ b/cmd/broadcast.go @@ -0,0 +1,282 @@ +package cmd + +import ( + "fmt" + "github.com/spf13/cobra" + jww "github.com/spf13/jwalterweatherman" + "github.com/spf13/viper" + "gitlab.com/elixxir/client/broadcast" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/rounds" + crypto "gitlab.com/elixxir/crypto/broadcast" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/primitives/utils" + "os" + "time" +) + +// singleCmd is the single-use subcommand that allows for sending and responding +// to single-use messages. +var broadcastCmd = &cobra.Command{ + Use: "broadcast", + Short: "Send broadcast messages", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + client := initClient() + + // Write user contact to file + user := client.GetUser() + jww.INFO.Printf("User: %s", user.ReceptionID) + jww.INFO.Printf("User Transmission: %s", user.TransmissionID) + writeContact(user.GetContact()) + + err := client.StartNetworkFollower(5 * time.Second) + if err != nil { + jww.FATAL.Panicf("Failed to start network follower: %+v", err) + } + + // Wait until connected or crash on timeout + connected := make(chan bool, 10) + client.GetCmix().AddHealthCallback( + func(isconnected bool) { + connected <- isconnected + }) + waitUntilConnected(connected) + + /* Set up underlying crypto broadcast.Channel */ + var channel *crypto.Channel + var pk *rsa.PrivateKey + keyPath := viper.GetString("keyPath") + path, err := utils.ExpandPath(viper.GetString("chanPath")) + if utils.Exists(path) { + // Load symmetric from path + cBytes, err := utils.ReadFile(path) + if err != nil { + jww.FATAL.Panicf("Failed to read channel from file at %s: %+v", path, err) + } + channel, err = crypto.UnmarshalChannel(cBytes) + if err != nil { + jww.FATAL.Panicf("Failed to unmarshal channel data %+v: %+v", cBytes, err) + } + } else { + // Load in broadcast channel info + name := viper.GetString("name") + desc := viper.GetString("description") + if name == "" { + jww.FATAL.Panicf("Name cannot be empty") + } else if desc == "" { + jww.FATAL.Panicf("description cannot be empty") + } + + var channel *crypto.Channel + if viper.GetBool("new") { + // Create a new broadcast channel + channel, pk, err = crypto.NewChannel(name, desc, client.GetRng().GetStream()) + if err != nil { + jww.FATAL.Panicf("Failed to create new channel: %+v", err) + } + + if keyPath != "" { + err = utils.WriteFile(path, rsa.CreatePrivateKeyPem(pk), os.ModePerm, os.ModeDir) + if err != nil { + jww.ERROR.Printf("Failed to write private key to path %s: %+v", path, err) + } + } else { + fmt.Printf("Private key generated for channel: %+v", rsa.CreatePrivateKeyPem(pk)) + } + } else { + // Read rest of info from config & build object manually + pubKeyBytes := []byte(viper.GetString("rsaPub")) + pubKey, err := rsa.LoadPublicKeyFromPem(pubKeyBytes) + if err != nil { + jww.FATAL.Panicf("Failed to load public key at path: %+v", err) + } + salt := []byte(viper.GetString("salt")) + + rid, err := crypto.NewChannelID(name, desc, salt, pubKeyBytes) + if err != nil { + jww.FATAL.Panicf("Failed to generate channel ID: %+v", err) + } + + channel = &crypto.Channel{ + ReceptionID: rid, + Name: name, + Description: desc, + Salt: salt, + RsaPubKey: pubKey, + } + + // Load key if it's there + if keyPath != "" { + if ep, err := utils.ExpandPath(keyPath); err == nil { + keyBytes, err := utils.ReadFile(ep) + if err != nil { + jww.ERROR.Printf("Failed to read private key from %s: %+v", ep, err) + } + pk, err = rsa.LoadPrivateKeyFromPem(keyBytes) + if err != nil { + jww.ERROR.Printf("Failed to load private key %+v: %+v", keyBytes, err) + } + } else { + jww.ERROR.Printf("Failed to expand private key path: %+v", err) + } + + } + } + + // Save channel to disk + cBytes, err := channel.Marshal() + if err != nil { + jww.ERROR.Printf("Failed to marshal channel to bytes: %+v", err) + } + // Write to file if there + if path != "" { + err = utils.WriteFile(path, cBytes, os.ModePerm, os.ModeDir) + if err != nil { + jww.ERROR.Printf("Failed to write channel to file %s: %+v", path, err) + } + } else { + fmt.Printf("Channel marshalled: %+v", cBytes) + } + } + + /* Broadcast client setup */ + + // Create receiver callback + receiveChan := make(chan []byte, 100) + cb := func(payload []byte, + receptionID receptionID.EphemeralIdentity, round rounds.Round) { + jww.INFO.Printf("Received symmetric message from %s over round %d", receptionID, round.ID) + receiveChan <- payload + } + + // Select broadcast method + var method broadcast.Method + symmetric := viper.GetBool("symmetric") + asymmetric := viper.GetBool("asymmetric") + if symmetric && asymmetric { + jww.FATAL.Panicf("Cannot simultaneously broadcast symmetric & asymmetric") + } + if symmetric { + method = broadcast.Symmetric + } else if asymmetric { + method = broadcast.Asymmetric + } + + // Connect to broadcast channel + bcl, err := broadcast.NewBroadcastChannel(*channel, cb, client.GetCmix(), client.GetRng(), broadcast.Param{Method: method}) + + /* Create properly sized broadcast message */ + message := viper.GetString("broadcast") + fmt.Println(message) + var broadcastMessage []byte + if message != "" { + broadcastMessage, err = broadcast.NewSizedBroadcast(bcl.MaxPayloadSize(), []byte(message)) + if err != nil { + jww.ERROR.Printf("Failed to create sized broadcast: %+v", err) + } + + } + + /* Broadcast message to the channel */ + switch method { + case broadcast.Symmetric: + rid, eid, err := bcl.Broadcast(broadcastMessage, cmix.GetDefaultCMIXParams()) + if err != nil { + jww.ERROR.Printf("Failed to send symmetric broadcast message: %+v", err) + } + jww.INFO.Printf("Sent symmetric broadcast message to %s over round %d", eid, rid) + case broadcast.Asymmetric: + if pk == nil { + jww.FATAL.Panicf("CANNOT SEND ASYMMETRIC BROADCAST WITHOUT PRIVATE KEY") + } + rid, eid, err := bcl.BroadcastAsymmetric(pk, broadcastMessage, cmix.GetDefaultCMIXParams()) + if err != nil { + jww.ERROR.Printf("Failed to send asymmetric broadcast message: %+v", err) + } + jww.INFO.Printf("Sent asymmetric broadcast message to %s over round %d", eid, rid) + default: + jww.WARN.Printf("Unknown broadcast type (this should not happen)") + } + + /* Receive broadcast messages over the channel */ + waitSecs := viper.GetUint("waitTimeout") + expectedCnt := viper.GetUint("receiveCount") + waitTimeout := time.Duration(waitSecs) * time.Second + receivedCount := uint(0) + done := false + for !done && expectedCnt != 0 { + timeout := time.NewTimer(waitTimeout) + select { + case receivedPayload := <-receiveChan: + receivedCount++ + receivedBroadcast, err := broadcast.DecodeSizedBroadcast(receivedPayload) + if err != nil { + jww.ERROR.Printf("Failed to decode sized broadcast: %+v", err) + continue + } + fmt.Printf("Symmetric broadcast message %d/%d received: %s\n", receivedCount, expectedCnt, string(receivedBroadcast)) + if receivedCount == expectedCnt { + done = true + } + case <-timeout.C: + fmt.Println("Timed out") + jww.ERROR.Printf("Timed out on message reception after %s!", waitTimeout) + done = true + } + } + + jww.INFO.Printf("Received %d/%d Messages!", receivedCount, expectedCnt) + bcl.Stop() + err = client.StopNetworkFollower() + if err != nil { + jww.WARN.Printf("Failed to cleanly close threads: %+v\n", err) + } + }, +} + +func init() { + // Single-use subcommand options + broadcastCmd.Flags().StringP("name", "", "", + "Symmetric channel name") + _ = viper.BindPFlag("name", broadcastCmd.Flags().Lookup("name")) + + broadcastCmd.Flags().StringP("rsaPub", "", "", + "Broadcast channel rsa pub key") + _ = viper.BindPFlag("rsaPub", broadcastCmd.Flags().Lookup("rsaPub")) + + broadcastCmd.Flags().StringP("salt", "", "", + "Broadcast channel salt") + _ = viper.BindPFlag("salt", broadcastCmd.Flags().Lookup("salt")) + + broadcastCmd.Flags().StringP("description", "", "", + "Broadcast channel description") + _ = viper.BindPFlag("description", broadcastCmd.Flags().Lookup("description")) + + broadcastCmd.Flags().StringP("chanPath", "", "", + "Broadcast channel output path") + _ = viper.BindPFlag("chanPath", broadcastCmd.Flags().Lookup("chanPath")) + + broadcastCmd.Flags().StringP("keyPath", "", "", + "Broadcast channel private key output path") + _ = viper.BindPFlag("keyPath", broadcastCmd.Flags().Lookup("keyPath")) + + broadcastCmd.Flags().BoolP("new", "", false, + "Create new broadcast channel") + _ = viper.BindPFlag("new", broadcastCmd.Flags().Lookup("new")) + + broadcastCmd.Flags().StringP("broadcast", "", "", + "Message contents for broadcast") + _ = viper.BindPFlag("broadcast", broadcastCmd.Flags().Lookup("broadcast")) + + broadcastCmd.Flags().BoolP("symmetric", "", false, + "Set broadcast method to symmetric") + _ = viper.BindPFlag("symmetric", broadcastCmd.Flags().Lookup("symmetric")) + + broadcastCmd.Flags().BoolP("asymmetric", "", false, + "Set broadcast method to asymmetric") + _ = viper.BindPFlag("asymmetric", broadcastCmd.Flags().Lookup("asymmetric")) + + rootCmd.AddCommand(broadcastCmd) +} diff --git a/cmd/root.go b/cmd/root.go index cc49169e05fddc183824c9e88b335135dc5c5ef2..aeca80079417004ec2e4a568bd594d2522ba5ec8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1071,9 +1071,9 @@ func init() { "Ensure successful message sending by checking for round completion") viper.BindPFlag("verify-sends", rootCmd.Flags().Lookup("verify-sends")) - rootCmd.Flags().UintP("receiveCount", + rootCmd.PersistentFlags().UintP("receiveCount", "", 1, "How many messages we should wait for before quitting") - viper.BindPFlag("receiveCount", rootCmd.Flags().Lookup("receiveCount")) + viper.BindPFlag("receiveCount", rootCmd.PersistentFlags().Lookup("receiveCount")) rootCmd.PersistentFlags().UintP("waitTimeout", "", 15, "The number of seconds to wait for messages to arrive") viper.BindPFlag("waitTimeout", diff --git a/cmix/gateway/hostPool.go b/cmix/gateway/hostPool.go index 25dc7192ed9933dcf323190a526c772a1548e8a3..09f52d5bd30eaae4d768e58eb0ae3a5120aa8cce 100644 --- a/cmix/gateway/hostPool.go +++ b/cmix/gateway/hostPool.go @@ -14,6 +14,7 @@ package gateway import ( "encoding/binary" + "encoding/json" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/storage" @@ -108,7 +109,18 @@ type PoolParams struct { ForceConnection bool // HostParams is the parameters for the creation of new Host objects. - HostParams connect.HostParams + //fixme params: have this adhere to json.Marshaler. + // This will allow the PoolParams object to have full adherence. + HostParams connect.HostParams `json:"-"` +} + +// poolParamsDisk will be the marshal-able and umarshal-able object. +type poolParamsDisk struct { + MaxPoolSize uint32 + PoolSize uint32 + ProxyAttempts uint32 + MaxPings uint32 + ForceConnection bool } // DefaultPoolParams returns a default set of PoolParams. @@ -132,6 +144,54 @@ func DefaultPoolParams() PoolParams { return p } +// GetParameters returns the default PoolParams, or +// override with given parameters, if set. +func GetParameters(params string) (PoolParams, error) { + p := DefaultPoolParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return PoolParams{}, err + } + } + return p, nil +} + +// MarshalJSON adheres to the json.Marshaler interface. +func (pp PoolParams) MarshalJSON() ([]byte, error) { + ppd := poolParamsDisk{ + MaxPoolSize: pp.MaxPoolSize, + PoolSize: pp.PoolSize, + ProxyAttempts: pp.ProxyAttempts, + MaxPings: pp.MaxPings, + ForceConnection: pp.ForceConnection, + } + + return json.Marshal(&ppd) +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (pp *PoolParams) UnmarshalJSON(data []byte) error { + ppDisk := poolParamsDisk{} + err := json.Unmarshal(data, &ppDisk) + if err != nil { + return err + } + + *pp = PoolParams{ + MaxPoolSize: ppDisk.MaxPoolSize, + PoolSize: ppDisk.PoolSize, + ProxyAttempts: ppDisk.ProxyAttempts, + MaxPings: ppDisk.MaxPings, + ForceConnection: ppDisk.ForceConnection, + // Since this does not adhere to json.Marshaler yet, + // file it in manually assuming default values + HostParams: connect.GetDefaultHostParams(), + } + + return nil +} + // newHostPool builds and returns a new HostPool object. func newHostPool(poolParams PoolParams, rng *fastRNG.StreamGenerator, netDef *ndf.NetworkDefinition, getter HostManager, storage storage.Session, diff --git a/cmix/identity/receptionID/store/unknownRounds.go b/cmix/identity/receptionID/store/unknownRounds.go index 022fd252dba2d7dbaaec14a072e5dbac6623ff25..b14c939abdd27e72a284bf37ba71e763b6bd0536 100644 --- a/cmix/identity/receptionID/store/unknownRounds.go +++ b/cmix/identity/receptionID/store/unknownRounds.go @@ -24,21 +24,6 @@ const ( defaultMaxCheck = 3 ) -// UnknownRounds tracks data for unknown rounds. Should adhere to UnknownRounds -// interface. -type UnknownRounds struct { - // Maps an unknown round to how many times the round has been checked - rounds map[id.Round]*uint64 - - // Configurations of UnknownRounds - params UnknownRoundsParams - - // Key Value store to save data to disk - kv *versioned.KV - - mux sync.Mutex -} - // UnknownRoundsParams allows configuration of UnknownRounds parameters. type UnknownRoundsParams struct { // MaxChecks is the maximum amount of checks of a round before that round @@ -49,6 +34,12 @@ type UnknownRoundsParams struct { Stored bool } +// unknownRoundsParamsDisk will be the marshal-able and umarshal-able object. +type unknownRoundsParamsDisk struct { + MaxChecks uint64 + Stored bool +} + // DefaultUnknownRoundsParams returns a default set of UnknownRoundsParams. func DefaultUnknownRoundsParams() UnknownRoundsParams { return UnknownRoundsParams{ @@ -57,6 +48,60 @@ func DefaultUnknownRoundsParams() UnknownRoundsParams { } } +// GetParameters returns the default UnknownRoundsParams, +// or override with given parameters, if set. +func GetParameters(params string) (UnknownRoundsParams, error) { + p := DefaultUnknownRoundsParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return UnknownRoundsParams{}, err + } + } + return p, nil +} + +// MarshalJSON adheres to the json.Marshaler interface. +func (urp UnknownRoundsParams) MarshalJSON() ([]byte, error) { + urpDisk := unknownRoundsParamsDisk{ + MaxChecks: urp.MaxChecks, + Stored: urp.Stored, + } + + return json.Marshal(&urpDisk) +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (urp *UnknownRoundsParams) UnmarshalJSON(data []byte) error { + urpDisk := unknownRoundsParamsDisk{} + err := json.Unmarshal(data, &urpDisk) + if err != nil { + return err + } + + *urp = UnknownRoundsParams{ + MaxChecks: urpDisk.MaxChecks, + Stored: urpDisk.Stored, + } + + return nil +} + +// UnknownRounds tracks data for unknown rounds. Should adhere to UnknownRounds +// interface. +type UnknownRounds struct { + // Maps an unknown round to how many times the round has been checked + rounds map[id.Round]*uint64 + + // Configurations of UnknownRounds + params UnknownRoundsParams + + // Key Value store to save data to disk + kv *versioned.KV + + mux sync.Mutex +} + // NewUnknownRounds builds and returns a new UnknownRounds object. func NewUnknownRounds(kv *versioned.KV, params UnknownRoundsParams) *UnknownRounds { diff --git a/cmix/message/params.go b/cmix/message/params.go index 9a3a56cd599860416ebe2951048c2c5ac7767987..cd2e6675bff9ad8eb6303b40c32a01f80ba152ea 100644 --- a/cmix/message/params.go +++ b/cmix/message/params.go @@ -1,7 +1,18 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + package message -import "time" +import ( + "encoding/json" + "time" +) +// Params contains the parameters for the message package. type Params struct { MessageReceptionBuffLen uint MessageReceptionWorkerPoolSize uint @@ -10,6 +21,17 @@ type Params struct { RealtimeOnly bool } +// paramsDisk will be the marshal-able and umarshal-able object. +type paramsDisk struct { + MessageReceptionBuffLen uint + MessageReceptionWorkerPoolSize uint + MaxChecksInProcessMessage uint + InProcessMessageWait time.Duration + RealtimeOnly bool +} + +// GetDefaultParams returns a Params object containing the +// default parameters. func GetDefaultParams() Params { return Params{ MessageReceptionBuffLen: 500, @@ -19,3 +41,49 @@ func GetDefaultParams() Params { RealtimeOnly: false, } } + +// GetParameters returns the default Params, or override with given +// parameters, if set. +func GetParameters(params string) (Params, error) { + p := GetDefaultParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return Params{}, err + } + } + return p, nil +} + +// MarshalJSON adheres to the json.Marshaler interface. +func (p Params) MarshalJSON() ([]byte, error) { + pDisk := paramsDisk{ + MessageReceptionBuffLen: p.MessageReceptionBuffLen, + MessageReceptionWorkerPoolSize: p.MessageReceptionWorkerPoolSize, + MaxChecksInProcessMessage: p.MaxChecksInProcessMessage, + InProcessMessageWait: p.InProcessMessageWait, + RealtimeOnly: p.RealtimeOnly, + } + + return json.Marshal(&pDisk) + +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (p *Params) UnmarshalJSON(data []byte) error { + pDisk := paramsDisk{} + err := json.Unmarshal(data, &pDisk) + if err != nil { + return err + } + + *p = Params{ + MessageReceptionBuffLen: pDisk.MessageReceptionBuffLen, + MessageReceptionWorkerPoolSize: pDisk.MessageReceptionWorkerPoolSize, + MaxChecksInProcessMessage: pDisk.MaxChecksInProcessMessage, + InProcessMessageWait: pDisk.InProcessMessageWait, + RealtimeOnly: pDisk.RealtimeOnly, + } + + return nil +} diff --git a/cmix/params.go b/cmix/params.go index d823632e199e021606a3414bc2403babcc3d17b9..d8aa778b104dadbdfa0e28215e00f0a10f5b6761 100644 --- a/cmix/params.go +++ b/cmix/params.go @@ -1,3 +1,10 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + package cmix import ( @@ -57,6 +64,26 @@ type Params struct { Historical rounds.Params } +// paramsDisk will be the marshal-able and umarshal-able object. +type paramsDisk struct { + TrackNetworkPeriod time.Duration + MaxCheckedRounds uint + RegNodesBufferLen uint + NetworkHealthTimeout time.Duration + ParallelNodeRegistrations uint + KnownRoundsThreshold uint + FastPolling bool + VerboseRoundTracking bool + RealtimeOnly bool + ReplayRequests bool + Rounds rounds.Params + Pickup pickup.Params + Message message.Params + Historical rounds.Params +} + +// GetDefaultParams returns a Params object containing the +// default parameters. func GetDefaultParams() Params { n := Params{ TrackNetworkPeriod: 100 * time.Millisecond, @@ -78,18 +105,7 @@ func GetDefaultParams() Params { return n } -func (n Params) Marshal() ([]byte, error) { - return json.Marshal(n) -} - -func (n Params) SetRealtimeOnlyAll() Params { - n.RealtimeOnly = true - n.Pickup.RealtimeOnly = true - n.Message.RealtimeOnly = true - return n -} - -// GetParameters returns the default network parameters, or override with given +// GetParameters returns the default Params, or override with given // parameters, if set. func GetParameters(params string) (Params, error) { p := GetDefaultParams() @@ -102,7 +118,62 @@ func GetParameters(params string) (Params, error) { return p, nil } -type NodeMap map[id.ID]bool +// MarshalJSON adheres to the json.Marshaler interface. +func (p Params) MarshalJSON() ([]byte, error) { + pDisk := paramsDisk{ + TrackNetworkPeriod: p.TrackNetworkPeriod, + MaxCheckedRounds: p.MaxCheckedRounds, + RegNodesBufferLen: p.RegNodesBufferLen, + NetworkHealthTimeout: p.NetworkHealthTimeout, + ParallelNodeRegistrations: p.ParallelNodeRegistrations, + KnownRoundsThreshold: p.KnownRoundsThreshold, + FastPolling: p.FastPolling, + VerboseRoundTracking: p.VerboseRoundTracking, + RealtimeOnly: p.RealtimeOnly, + ReplayRequests: p.ReplayRequests, + Rounds: p.Rounds, + Pickup: p.Pickup, + Message: p.Message, + Historical: p.Historical, + } + + return json.Marshal(&pDisk) +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (p *Params) UnmarshalJSON(data []byte) error { + pDisk := paramsDisk{} + err := json.Unmarshal(data, &pDisk) + if err != nil { + return err + } + + *p = Params{ + TrackNetworkPeriod: pDisk.TrackNetworkPeriod, + MaxCheckedRounds: pDisk.MaxCheckedRounds, + RegNodesBufferLen: pDisk.RegNodesBufferLen, + NetworkHealthTimeout: pDisk.NetworkHealthTimeout, + ParallelNodeRegistrations: pDisk.ParallelNodeRegistrations, + KnownRoundsThreshold: pDisk.KnownRoundsThreshold, + FastPolling: pDisk.FastPolling, + VerboseRoundTracking: pDisk.VerboseRoundTracking, + RealtimeOnly: pDisk.RealtimeOnly, + ReplayRequests: pDisk.ReplayRequests, + Rounds: pDisk.Rounds, + Pickup: pDisk.Pickup, + Message: pDisk.Message, + Historical: pDisk.Historical, + } + + return nil +} + +func (p Params) SetRealtimeOnlyAll() Params { + p.RealtimeOnly = true + p.Pickup.RealtimeOnly = true + p.Message.RealtimeOnly = true + return p +} const DefaultDebugTag = "External" @@ -137,6 +208,17 @@ type CMIXParams struct { Critical bool } +// cMixParamsDisk will be the marshal-able and umarshal-able object. +type cMixParamsDisk struct { + RoundTries uint + Timeout time.Duration + RetryDelay time.Duration + SendTimeout time.Duration + DebugTag string + BlacklistedNodes NodeMap + Critical bool +} + func GetDefaultCMIXParams() CMIXParams { return CMIXParams{ RoundTries: 10, @@ -163,6 +245,47 @@ func GetCMIXParameters(params string) (CMIXParams, error) { return p, nil } +// MarshalJSON adheres to the json.Marshaler interface. +func (p CMIXParams) MarshalJSON() ([]byte, error) { + pDisk := cMixParamsDisk{ + RoundTries: p.RoundTries, + Timeout: p.Timeout, + RetryDelay: p.RetryDelay, + SendTimeout: p.SendTimeout, + DebugTag: p.DebugTag, + Critical: p.Critical, + BlacklistedNodes: p.BlacklistedNodes, + } + + return json.Marshal(&pDisk) + +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (p *CMIXParams) UnmarshalJSON(data []byte) error { + pDisk := cMixParamsDisk{} + err := json.Unmarshal(data, &pDisk) + if err != nil { + return err + } + + *p = CMIXParams{ + RoundTries: pDisk.RoundTries, + Timeout: pDisk.Timeout, + RetryDelay: pDisk.RetryDelay, + SendTimeout: pDisk.SendTimeout, + DebugTag: pDisk.DebugTag, + Critical: pDisk.Critical, + BlacklistedNodes: pDisk.BlacklistedNodes, + } + + return nil +} + +// NodeMap represents a map of nodes and whether they have been +// blacklisted. This is designed for use with CMIXParams.BlacklistedNodes +type NodeMap map[id.ID]bool + // MarshalJSON adheres to the json.Marshaler interface. func (nm NodeMap) MarshalJSON() ([]byte, error) { stringMap := make(map[string]bool, len(nm)) diff --git a/cmix/params_test.go b/cmix/params_test.go index bb19499b3640c5234ece0b9a14d2cd7789649872..90ece9d5f6ff0dbf23a528832a0adc297425818e 100644 --- a/cmix/params_test.go +++ b/cmix/params_test.go @@ -8,6 +8,7 @@ package cmix import ( + "bytes" "encoding/json" "gitlab.com/xx_network/primitives/id" "reflect" @@ -15,6 +16,43 @@ import ( "time" ) +// Tests that no data is lost when marshaling and +// unmarshaling the Params object. +func TestParams_MarshalUnmarshal(t *testing.T) { + // Construct a set of params + p := GetDefaultParams() + + // Marshal the params + data, err := json.Marshal(&p) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + + t.Logf("%s", string(data)) + + // Unmarshal the params object + received := Params{} + err = json.Unmarshal(data, &received) + if err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + + // Re-marshal this params object + data2, err := json.Marshal(received) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + + t.Logf("%s", string(data2)) + + // Check that they match (it is done this way to avoid + // false failures with the reflect.DeepEqual function and + // pointers) + if !bytes.Equal(data, data2) { + t.Fatalf("Data was lost in marshal/unmarshal.") + } +} + func TestCMIXParams_JSON_Marshal_Unmarshal(t *testing.T) { p := CMIXParams{ RoundTries: 5, diff --git a/cmix/pickup/params.go b/cmix/pickup/params.go index 95200674d0e0e8a791a0236e1a5dc019b4b59225..99ec4cb835557f411d3d88cdf07acc23e40dba14 100644 --- a/cmix/pickup/params.go +++ b/cmix/pickup/params.go @@ -1,7 +1,18 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + package pickup -import "time" +import ( + "encoding/json" + "time" +) +// Params contains the parameters for the pickup package. type Params struct { // Number of worker threads for retrieving messages from gateways NumMessageRetrievalWorkers uint @@ -31,6 +42,19 @@ type Params struct { ForceHistoricalRounds bool } +// paramsDisk will be the marshal-able and umarshal-able object. +type paramsDisk struct { + NumMessageRetrievalWorkers uint + LookupRoundsBufferLen uint + MaxHistoricalRoundsRetries uint + UncheckRoundPeriod time.Duration + ForceMessagePickupRetry bool + SendTimeout time.Duration + RealtimeOnly bool + ForceHistoricalRounds bool +} + +// GetDefaultParams returns a default set of Params. func GetDefaultParams() Params { return Params{ NumMessageRetrievalWorkers: 8, @@ -42,3 +66,55 @@ func GetDefaultParams() Params { RealtimeOnly: false, } } + +// GetParameters returns the default Params, or override with given +// parameters, if set. +func GetParameters(params string) (Params, error) { + p := GetDefaultParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return Params{}, err + } + } + return p, nil +} + +// MarshalJSON adheres to the json.Marshaler interface. +func (p Params) MarshalJSON() ([]byte, error) { + pDisk := paramsDisk{ + NumMessageRetrievalWorkers: p.NumMessageRetrievalWorkers, + LookupRoundsBufferLen: p.LookupRoundsBufferLen, + MaxHistoricalRoundsRetries: p.MaxHistoricalRoundsRetries, + UncheckRoundPeriod: p.UncheckRoundPeriod, + ForceMessagePickupRetry: p.ForceMessagePickupRetry, + SendTimeout: p.SendTimeout, + RealtimeOnly: p.RealtimeOnly, + ForceHistoricalRounds: p.ForceHistoricalRounds, + } + + return json.Marshal(&pDisk) + +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (p *Params) UnmarshalJSON(data []byte) error { + pDisk := paramsDisk{} + err := json.Unmarshal(data, &pDisk) + if err != nil { + return err + } + + *p = Params{ + NumMessageRetrievalWorkers: pDisk.NumMessageRetrievalWorkers, + LookupRoundsBufferLen: pDisk.LookupRoundsBufferLen, + MaxHistoricalRoundsRetries: pDisk.MaxHistoricalRoundsRetries, + UncheckRoundPeriod: pDisk.UncheckRoundPeriod, + ForceMessagePickupRetry: pDisk.ForceMessagePickupRetry, + SendTimeout: pDisk.SendTimeout, + RealtimeOnly: pDisk.RealtimeOnly, + ForceHistoricalRounds: pDisk.ForceHistoricalRounds, + } + + return nil +} diff --git a/cmix/rounds/params.go b/cmix/rounds/params.go index 1023f6022acce31a971747c5f31bfebdec724c03..dd9596c2fcf6809010ceb9fbeda609f2ef3b6645 100644 --- a/cmix/rounds/params.go +++ b/cmix/rounds/params.go @@ -1,7 +1,18 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + package rounds -import "time" +import ( + "encoding/json" + "time" +) +// Params contains the parameters for the rounds package. type Params struct { // MaxHistoricalRounds is the number of historical rounds required to // automatically send a historical rounds query. @@ -20,6 +31,15 @@ type Params struct { MaxHistoricalRoundsRetries uint } +// paramsDisk will be the marshal-able and umarshal-able object. +type paramsDisk struct { + MaxHistoricalRounds uint + HistoricalRoundsPeriod time.Duration + HistoricalRoundsBufferLen uint + MaxHistoricalRoundsRetries uint +} + +// GetDefaultParams returns a default set of Params. func GetDefaultParams() Params { return Params{ MaxHistoricalRounds: 100, @@ -28,3 +48,47 @@ func GetDefaultParams() Params { MaxHistoricalRoundsRetries: 3, } } + +// GetParameters returns the default Params, or override with given +// parameters, if set. +func GetParameters(params string) (Params, error) { + p := GetDefaultParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return Params{}, err + } + } + return p, nil +} + +// MarshalJSON adheres to the json.Marshaler interface. +func (r Params) MarshalJSON() ([]byte, error) { + pDisk := paramsDisk{ + MaxHistoricalRounds: r.MaxHistoricalRounds, + HistoricalRoundsPeriod: r.HistoricalRoundsPeriod, + HistoricalRoundsBufferLen: r.HistoricalRoundsBufferLen, + MaxHistoricalRoundsRetries: r.MaxHistoricalRoundsRetries, + } + + return json.Marshal(&pDisk) + +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (r *Params) UnmarshalJSON(data []byte) error { + pDisk := paramsDisk{} + err := json.Unmarshal(data, &pDisk) + if err != nil { + return err + } + + *r = Params{ + MaxHistoricalRounds: pDisk.MaxHistoricalRounds, + HistoricalRoundsPeriod: pDisk.HistoricalRoundsPeriod, + HistoricalRoundsBufferLen: pDisk.HistoricalRoundsBufferLen, + MaxHistoricalRoundsRetries: pDisk.MaxHistoricalRoundsRetries, + } + + return nil +} diff --git a/connect/connect.go b/connect/connect.go index e5c544672de6358aac88a5fbe331880c50f74a43..310ef304a868f769068ac1d8d95b82ceb28b2945 100644 --- a/connect/connect.go +++ b/connect/connect.go @@ -7,6 +7,7 @@ package connect import ( + "encoding/json" "io" "time" @@ -84,9 +85,9 @@ type Callback func(connection Connection) // Params for managing Connection objects. type Params struct { - Auth auth.Param + Auth auth.Params Rekey rekey.Params - Event event.Reporter + Event event.Reporter `json:"-"` Timeout time.Duration } @@ -100,6 +101,19 @@ func GetDefaultParams() Params { } } +// GetParameters returns the default Params, or override with given +// parameters, if set. +func GetParameters(params string) (Params, error) { + p := GetDefaultParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return Params{}, err + } + } + return p, nil +} + // Connect performs auth key negotiation with the given recipient, // and returns a Connection object for the newly-created partner.Manager // This function is to be used sender-side and will block until the diff --git a/connect/params_test.go b/connect/params_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5501ad55153af983a72df2b0ab4d1e8b64358628 --- /dev/null +++ b/connect/params_test.go @@ -0,0 +1,49 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + "bytes" + "encoding/json" + "testing" +) + +func TestParams_MarshalUnmarshal(t *testing.T) { + // Construct a set of params + p := GetDefaultParams() + + // Marshal the params + data, err := json.Marshal(&p) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + + t.Logf("%s", string(data)) + + // Unmarshal the params object + received := Params{} + err = json.Unmarshal(data, &received) + if err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + + // Re-marshal this params object + data2, err := json.Marshal(received) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + + t.Logf("%s", string(data2)) + + // Check that they match (it is done this way to avoid + // false failures with the reflect.DeepEqual function and + // pointers) + if !bytes.Equal(data, data2) { + t.Fatalf("Data was lost in marshal/unmarshal.") + } +} diff --git a/e2e/params.go b/e2e/params.go index 23052727ffcc103f62e14c5d41fad2fbcabfe244..2702d490656342e975883177b115f63dbf2df961 100644 --- a/e2e/params.go +++ b/e2e/params.go @@ -29,6 +29,17 @@ type Params struct { cmix.CMIXParams } +// paramsDisk will be the marshal-able and umarshal-able object. +type paramsDisk struct { + ServiceTag string + LastServiceTag string + KeyGetRetryCount uint + KeyGeRetryDelay time.Duration + Rekey bool + cmix.CMIXParams +} + +// GetDefaultParams returns a default set of Params. func GetDefaultParams() Params { return Params{ ServiceTag: catalog.Silent, @@ -41,12 +52,9 @@ func GetDefaultParams() Params { CMIXParams: cmix.GetDefaultCMIXParams(), } } -func (e Params) Marshal() ([]byte, error) { - return json.Marshal(e) -} -// GetParameters Obtain default E2E parameters, or override with -// given parameters if set +// GetParameters Obtain default Params, or override with +// given parameters if set. func GetParameters(params string) (Params, error) { p := GetDefaultParams() if len(params) > 0 { @@ -57,3 +65,38 @@ func GetParameters(params string) (Params, error) { } return p, nil } + +// MarshalJSON adheres to the json.Marshaler interface. +func (p Params) MarshalJSON() ([]byte, error) { + pDisk := paramsDisk{ + ServiceTag: p.ServiceTag, + LastServiceTag: p.LastServiceTag, + KeyGetRetryCount: p.KeyGetRetryCount, + KeyGeRetryDelay: p.KeyGeRetryDelay, + Rekey: p.Rekey, + CMIXParams: p.CMIXParams, + } + + return json.Marshal(&pDisk) + +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (p *Params) UnmarshalJSON(data []byte) error { + pDisk := paramsDisk{} + err := json.Unmarshal(data, &pDisk) + if err != nil { + return err + } + + *p = Params{ + ServiceTag: pDisk.ServiceTag, + LastServiceTag: pDisk.LastServiceTag, + KeyGetRetryCount: pDisk.KeyGetRetryCount, + KeyGeRetryDelay: pDisk.KeyGeRetryDelay, + Rekey: pDisk.Rekey, + CMIXParams: pDisk.CMIXParams, + } + + return nil +} diff --git a/e2e/ratchet/partner/session/params.go b/e2e/ratchet/partner/session/params.go index 8ccd454947ff281471cd77d79fa15ea826efec9d..2782578e9e605d117d2bb872f40822816c0fde2f 100644 --- a/e2e/ratchet/partner/session/params.go +++ b/e2e/ratchet/partner/session/params.go @@ -1,6 +1,20 @@ package session -import "fmt" +import ( + "encoding/json" + "fmt" +) + +// DEFAULT KEY GENERATION PARAMETERS +// Hardcoded limits for keys +// sets the number of keys very high, but with a low rekey threshold. In this case, if the other party is online, you will read +const ( + minKeys uint16 = 1000 + maxKeys uint16 = 2000 + rekeyThrshold float64 = 0.05 + numReKeys uint16 = 16 + rekeyRatio float64 = 1 / 10 +) type Params struct { // using the DH as a seed, both sides finalizeKeyNegotation a number @@ -20,17 +34,16 @@ type Params struct { UnconfirmedRetryRatio float64 } -// DEFAULT KEY GENERATION PARAMETERS -// Hardcoded limits for keys -// sets the number of keys very high, but with a low rekey threshold. In this case, if the other party is online, you will read -const ( - minKeys uint16 = 1000 - maxKeys uint16 = 2000 - rekeyThrshold float64 = 0.05 - numReKeys uint16 = 16 - rekeyRatio float64 = 1 / 10 -) +// paramsDisk will be the marshal-able and umarshal-able object. +type paramsDisk struct { + MinKeys uint16 + MaxKeys uint16 + RekeyThreshold float64 + NumRekeys uint16 + UnconfirmedRetryRatio float64 +} +// GetDefaultParams returns a default set of Params. func GetDefaultParams() Params { return Params{ MinKeys: minKeys, @@ -41,6 +54,50 @@ func GetDefaultParams() Params { } } +// GetParameters returns the default Params, or override with given +// parameters, if set. +func GetParameters(params string) (Params, error) { + p := GetDefaultParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return Params{}, err + } + } + return p, nil +} + +// MarshalJSON adheres to the json.Marshaler interface. +func (p Params) MarshalJSON() ([]byte, error) { + pDisk := paramsDisk{ + MinKeys: p.MinKeys, + MaxKeys: p.MaxKeys, + RekeyThreshold: p.RekeyThreshold, + NumRekeys: p.NumRekeys, + UnconfirmedRetryRatio: p.UnconfirmedRetryRatio, + } + return json.Marshal(&pDisk) + +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (p *Params) UnmarshalJSON(data []byte) error { + pDisk := paramsDisk{} + err := json.Unmarshal(data, &pDisk) + if err != nil { + return err + } + + *p = Params{ + MinKeys: pDisk.MinKeys, + MaxKeys: pDisk.MaxKeys, + RekeyThreshold: pDisk.RekeyThreshold, + NumRekeys: pDisk.NumRekeys, + UnconfirmedRetryRatio: pDisk.UnconfirmedRetryRatio, + } + return nil +} + func (p Params) String() string { return fmt.Sprintf("SessionParams{ MinKeys: %d, MaxKeys: %d, NumRekeys: %d }", p.MinKeys, p.MaxKeys, p.NumRekeys) diff --git a/e2e/rekey/params.go b/e2e/rekey/params.go index 0611e67e84b4875bae35f29855fbddc0e007fe83..274770c112329cb552a85df55c6abc6d273cf67c 100644 --- a/e2e/rekey/params.go +++ b/e2e/rekey/params.go @@ -1,6 +1,7 @@ package rekey import ( + "encoding/json" "gitlab.com/elixxir/client/catalog" "time" ) @@ -22,6 +23,17 @@ type Params struct { StoppableName string } +// paramsDisk will be the marshal-able and umarshal-able object. +type paramsDisk struct { + RoundTimeout time.Duration + TriggerName string + Trigger catalog.MessageType + ConfirmName string + Confirm catalog.MessageType + StoppableName string +} + +// GetDefaultParams returns a default set of Params. func GetDefaultParams() Params { return Params{ RoundTimeout: time.Minute, @@ -33,6 +45,8 @@ func GetDefaultParams() Params { } } +// GetDefaultEphemeralParams returns a default set of Params for +// ephemeral re-keying. func GetDefaultEphemeralParams() Params { p := GetDefaultParams() p.TriggerName = keyExchangeTriggerEphemeralName @@ -42,3 +56,50 @@ func GetDefaultEphemeralParams() Params { p.StoppableName = keyExchangeEphemeralMulti return p } + +// GetParameters returns the default network parameters, or override with given +// parameters, if set. +func GetParameters(params string) (Params, error) { + p := GetDefaultParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return Params{}, err + } + } + return p, nil +} + +// MarshalJSON adheres to the json.Marshaler interface. +func (p Params) MarshalJSON() ([]byte, error) { + pDisk := paramsDisk{ + RoundTimeout: p.RoundTimeout, + TriggerName: p.TriggerName, + Trigger: p.Trigger, + ConfirmName: p.ConfirmName, + Confirm: p.Confirm, + StoppableName: p.StoppableName, + } + return json.Marshal(&pDisk) + +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (p *Params) UnmarshalJSON(data []byte) error { + pDisk := paramsDisk{} + err := json.Unmarshal(data, &pDisk) + if err != nil { + return err + } + + *p = Params{ + RoundTimeout: pDisk.RoundTimeout, + TriggerName: pDisk.TriggerName, + Trigger: pDisk.Trigger, + ConfirmName: pDisk.ConfirmName, + Confirm: pDisk.Confirm, + StoppableName: pDisk.StoppableName, + } + + return nil +} diff --git a/fileTransfer/params.go b/fileTransfer/params.go index 754bb88db3d45c75f3e47454f45bf4cf6170e830..8e54d5d02c9430224f526008d9138cb35e08e1ce 100644 --- a/fileTransfer/params.go +++ b/fileTransfer/params.go @@ -7,7 +7,10 @@ package fileTransfer -import "time" +import ( + "encoding/json" + "time" +) const ( defaultMaxThroughput = 150_000 // 150 kB per second @@ -26,6 +29,12 @@ type Params struct { SendTimeout time.Duration } +// paramsDisk will be the marshal-able and umarshal-able object. +type paramsDisk struct { + MaxThroughput int + SendTimeout time.Duration +} + // DefaultParams returns a Params object filled with the default values. func DefaultParams() Params { return Params{ @@ -33,3 +42,42 @@ func DefaultParams() Params { SendTimeout: defaultSendTimeout, } } + +// GetParameters returns the default network parameters, or override with given +// parameters, if set. +func GetParameters(params string) (Params, error) { + p := DefaultParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return Params{}, err + } + } + return p, nil +} + +// MarshalJSON adheres to the json.Marshaler interface. +func (p Params) MarshalJSON() ([]byte, error) { + pDisk := paramsDisk{ + MaxThroughput: p.MaxThroughput, + SendTimeout: p.SendTimeout, + } + + return json.Marshal(&pDisk) +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (p *Params) UnmarshalJSON(data []byte) error { + pDisk := paramsDisk{} + err := json.Unmarshal(data, &pDisk) + if err != nil { + return err + } + + *p = Params{ + MaxThroughput: pDisk.MaxThroughput, + SendTimeout: pDisk.SendTimeout, + } + + return nil +} diff --git a/fileTransfer2/connect/params.go b/fileTransfer2/connect/params.go index 3b502637e895a852b8ab11c0118d0806659c073a..93a3a44a1f6a6d001724875bd85ba5fd13374a54 100644 --- a/fileTransfer2/connect/params.go +++ b/fileTransfer2/connect/params.go @@ -7,6 +7,10 @@ package connect +import ( + "encoding/json" +) + const ( defaultNotifyUponCompletion = true ) @@ -18,9 +22,47 @@ type Params struct { NotifyUponCompletion bool } +// paramsDisk will be the marshal-able and umarshal-able object. +type paramsDisk struct { + NotifyUponCompletion bool +} + // DefaultParams returns a Params object filled with the default values. func DefaultParams() Params { return Params{ NotifyUponCompletion: defaultNotifyUponCompletion, } } + +// GetParameters returns the default Params, or override with given +// parameters, if set. +func GetParameters(params string) (Params, error) { + p := DefaultParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return Params{}, err + } + } + return p, nil +} + +// MarshalJSON adheres to the json.Marshaler interface. +func (p Params) MarshalJSON() ([]byte, error) { + pDisk := paramsDisk{NotifyUponCompletion: p.NotifyUponCompletion} + return json.Marshal(&pDisk) + +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (p *Params) UnmarshalJSON(data []byte) error { + pDisk := paramsDisk{} + err := json.Unmarshal(data, &pDisk) + if err != nil { + return err + } + + *p = Params{NotifyUponCompletion: pDisk.NotifyUponCompletion} + + return nil +} diff --git a/fileTransfer2/e2e/params.go b/fileTransfer2/e2e/params.go index 254e7d3a40777ec48c8ad33c89b95ccacaf5d2d9..819e963f5fd9efc3d058b5a953e87c7aa8874f1f 100644 --- a/fileTransfer2/e2e/params.go +++ b/fileTransfer2/e2e/params.go @@ -7,6 +7,8 @@ package e2e +import "encoding/json" + const ( defaultNotifyUponCompletion = true ) @@ -18,9 +20,47 @@ type Params struct { NotifyUponCompletion bool } +// paramsDisk will be the marshal-able and umarshal-able object. +type paramsDisk struct { + NotifyUponCompletion bool +} + // DefaultParams returns a Params object filled with the default values. func DefaultParams() Params { return Params{ NotifyUponCompletion: defaultNotifyUponCompletion, } } + +// GetParameters returns the default Params, or override with given +// parameters, if set. +func GetParameters(params string) (Params, error) { + p := DefaultParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return Params{}, err + } + } + return p, nil +} + +// MarshalJSON adheres to the json.Marshaler interface. +func (p Params) MarshalJSON() ([]byte, error) { + pDisk := paramsDisk{NotifyUponCompletion: p.NotifyUponCompletion} + return json.Marshal(&pDisk) + +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (p *Params) UnmarshalJSON(data []byte) error { + pDisk := paramsDisk{} + err := json.Unmarshal(data, &pDisk) + if err != nil { + return err + } + + *p = Params{NotifyUponCompletion: pDisk.NotifyUponCompletion} + + return nil +} diff --git a/fileTransfer2/params.go b/fileTransfer2/params.go index b4f1587bec6584e143b65d70d572cf740921bbb4..4a70046560b0e2793620f8bf29d5c1c62078b6f1 100644 --- a/fileTransfer2/params.go +++ b/fileTransfer2/params.go @@ -8,6 +8,7 @@ package fileTransfer2 import ( + "encoding/json" "gitlab.com/elixxir/client/cmix" "time" ) @@ -32,6 +33,13 @@ type Params struct { Cmix cmix.CMIXParams } +// paramsDisk will be the marshal-able and umarshal-able object. +type paramsDisk struct { + MaxThroughput int + SendTimeout time.Duration + Cmix cmix.CMIXParams +} + // DefaultParams returns a Params object filled with the default values. func DefaultParams() Params { return Params{ @@ -40,3 +48,45 @@ func DefaultParams() Params { Cmix: cmix.GetDefaultCMIXParams(), } } + +// GetParameters returns the default network parameters, or override with given +// parameters, if set. +func GetParameters(params string) (Params, error) { + p := DefaultParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return Params{}, err + } + } + return p, nil +} + +// MarshalJSON adheres to the json.Marshaler interface. +func (p Params) MarshalJSON() ([]byte, error) { + pDisk := paramsDisk{ + MaxThroughput: p.MaxThroughput, + SendTimeout: p.SendTimeout, + Cmix: p.Cmix, + } + + return json.Marshal(&pDisk) + +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (p *Params) UnmarshalJSON(data []byte) error { + pDisk := paramsDisk{} + err := json.Unmarshal(data, &pDisk) + if err != nil { + return err + } + + *p = Params{ + MaxThroughput: pDisk.MaxThroughput, + SendTimeout: pDisk.SendTimeout, + Cmix: pDisk.Cmix, + } + + return nil +} diff --git a/fileTransfer2/params_test.go b/fileTransfer2/params_test.go index 849db501a81c9639e12890f8337a55ef85165263..8bbeb942b4f1c5f1d70e418ebf1c5f796dd84d4a 100644 --- a/fileTransfer2/params_test.go +++ b/fileTransfer2/params_test.go @@ -8,11 +8,51 @@ package fileTransfer2 import ( + "bytes" + "encoding/json" "gitlab.com/elixxir/client/cmix" "reflect" "testing" ) +// Tests that no data is lost when marshaling and +// unmarshaling the Params object. +func TestParams_MarshalUnmarshal(t *testing.T) { + // Construct a set of params + p := DefaultParams() + + // Marshal the params + data, err := json.Marshal(&p) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + + t.Logf("%s", string(data)) + + // Unmarshal the params object + received := Params{} + err = json.Unmarshal(data, &received) + if err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + + // Re-marshal this params object + data2, err := json.Marshal(received) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + + t.Logf("%s", string(data2)) + + // Check that they match (it is done this way to avoid + // false failures with the reflect.DeepEqual function and + // pointers) + if !bytes.Equal(data, data2) { + t.Fatalf("Data was lost in marshal/unmarshal.") + } + +} + // Tests that DefaultParams returns a Params object with the expected defaults. func TestDefaultParams(t *testing.T) { expected := Params{ diff --git a/go.mod b/go.mod index 5cc7bdbc98c27ad9aad0f873ef32a516cc24c9f8..45cbd29359501276d3da18b7745794d21cdaaa8c 100644 --- a/go.mod +++ b/go.mod @@ -13,11 +13,11 @@ require ( github.com/spf13/viper v1.7.1 gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f gitlab.com/elixxir/comms v0.0.4-0.20220323190139-9ed75f3a8b2c - gitlab.com/elixxir/crypto v0.0.7-0.20220425192911-a23209a58073 + gitlab.com/elixxir/crypto v0.0.7-0.20220516144816-71049ce09e4b gitlab.com/elixxir/ekv v0.1.7 gitlab.com/elixxir/primitives v0.0.3-0.20220330212736-cce83b5f948f gitlab.com/xx_network/comms v0.0.4-0.20220315161313-76acb14429ac - gitlab.com/xx_network/crypto v0.0.5-0.20220317171841-084640957d71 + gitlab.com/xx_network/crypto v0.0.5-0.20220516143655-14f9153096ce gitlab.com/xx_network/primitives v0.0.4-0.20220324193139-b292d1ae6e7e go.uber.org/ratelimit v0.2.0 golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed diff --git a/go.sum b/go.sum index c8be2cdbd0727dfdcf41420df6ab683d01cdcf8b..c3aa45ee09d936b71b808d039ce87d0ea1bd0b5d 100644 --- a/go.sum +++ b/go.sum @@ -434,6 +434,14 @@ gitlab.com/elixxir/crypto v0.0.7-0.20220425192754-f9f5ca6ad2eb h1:K10bnRhnZS6XFx gitlab.com/elixxir/crypto v0.0.7-0.20220425192754-f9f5ca6ad2eb/go.mod h1:JkByWX/TXCjdu6pRJsx+jwttbBGvlAljYSJMImDmt+4= gitlab.com/elixxir/crypto v0.0.7-0.20220425192911-a23209a58073 h1:T0BK30t1F9M8RCdsuMN1bL34fSLNPqjqC+oufnjPh3I= gitlab.com/elixxir/crypto v0.0.7-0.20220425192911-a23209a58073/go.mod h1:JkByWX/TXCjdu6pRJsx+jwttbBGvlAljYSJMImDmt+4= +gitlab.com/elixxir/crypto v0.0.7-0.20220509151343-6e5a514a65fd h1:3lU8kF5ItUZYIuKHF6qrC3o1yiL6fKdF5p1HJRB9iik= +gitlab.com/elixxir/crypto v0.0.7-0.20220509151343-6e5a514a65fd/go.mod h1:cJF80ad9YCR+UcOlZNzfDVBAQqGEEhhs3y5taMEvXaE= +gitlab.com/elixxir/crypto v0.0.7-0.20220510191648-70e5e956d3d5 h1:uzzFrmqx0CnqQ7AInGi6PA8w1mm2032hIpYn2G2SDKA= +gitlab.com/elixxir/crypto v0.0.7-0.20220510191648-70e5e956d3d5/go.mod h1:cJF80ad9YCR+UcOlZNzfDVBAQqGEEhhs3y5taMEvXaE= +gitlab.com/elixxir/crypto v0.0.7-0.20220513133141-46faee2d8fca h1:0EINaZCe/0dPbQD2msOj8fwKpZ5lnZ57Y6zUZm85rEo= +gitlab.com/elixxir/crypto v0.0.7-0.20220513133141-46faee2d8fca/go.mod h1:cJF80ad9YCR+UcOlZNzfDVBAQqGEEhhs3y5taMEvXaE= +gitlab.com/elixxir/crypto v0.0.7-0.20220516144816-71049ce09e4b h1:HZlcbi+rTr1MACtLoNUJDV2iTvy7JtkRjWWaPF45bcM= +gitlab.com/elixxir/crypto v0.0.7-0.20220516144816-71049ce09e4b/go.mod h1:L3duHa+GppnsY8x22/ixrEdQSM7WXa1ORWPS90HZ/JI= gitlab.com/elixxir/ekv v0.1.6 h1:M2hUSNhH/ChxDd+s8xBqSEKgoPtmE6hOEBqQ73KbN6A= gitlab.com/elixxir/ekv v0.1.6/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4= gitlab.com/elixxir/ekv v0.1.7 h1:OW2z+N4QCqqMFzouAwFTWWMKz0Y/PDhyYReN7gQ5NiQ= @@ -462,6 +470,10 @@ gitlab.com/xx_network/crypto v0.0.5-0.20220222212031-750f7e8a01f4 h1:95dZDMn/hpL gitlab.com/xx_network/crypto v0.0.5-0.20220222212031-750f7e8a01f4/go.mod h1:6apvsoHCQJDjO0J4E3uhR3yO9tTz/Mq5be5rjB3tQPU= gitlab.com/xx_network/crypto v0.0.5-0.20220317171841-084640957d71 h1:N2+Jja4xNg66entu6rGvzRcf3Vc785xgiaHeDPYnBvg= gitlab.com/xx_network/crypto v0.0.5-0.20220317171841-084640957d71/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk= +gitlab.com/xx_network/crypto v0.0.5-0.20220502201458-dabab1ef2982 h1:iUMwO/lIeOEmqNTMPtr9d7H6Y0cGe4DaHhd+ISCts6E= +gitlab.com/xx_network/crypto v0.0.5-0.20220502201458-dabab1ef2982/go.mod h1:/SJf+R75E+QepdTLh0H1/udsovxx2Q5ru34q1v0umKk= +gitlab.com/xx_network/crypto v0.0.5-0.20220516143655-14f9153096ce h1:zkPtJLJUpoWDYD4z30nmKU8OvnIOnBvSu7e6q6WhY5A= +gitlab.com/xx_network/crypto v0.0.5-0.20220516143655-14f9153096ce/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/single/params.go b/single/params.go index 10d60a908022f6d7c0e4857133a8e7a7b8444306..8895818449ba626ea69a112226a8afd182cdc5f3 100644 --- a/single/params.go +++ b/single/params.go @@ -8,6 +8,7 @@ package single import ( + "encoding/json" "gitlab.com/elixxir/client/cmix" "time" ) @@ -32,6 +33,13 @@ type RequestParams struct { CmixParams cmix.CMIXParams } +// requestParamsDisk will be the marshal-able and umarshal-able object. +type requestParamsDisk struct { + Timeout time.Duration + MaxResponseMessages uint8 + CmixParams cmix.CMIXParams +} + // GetDefaultRequestParams returns a RequestParams with the default // configuration. func GetDefaultRequestParams() RequestParams { @@ -41,3 +49,45 @@ func GetDefaultRequestParams() RequestParams { CmixParams: cmix.GetDefaultCMIXParams(), } } + +// GetParameters returns the default network parameters, or override with given +// parameters, if set. +func GetParameters(params string) (RequestParams, error) { + p := GetDefaultRequestParams() + if len(params) > 0 { + err := json.Unmarshal([]byte(params), &p) + if err != nil { + return RequestParams{}, err + } + } + return p, nil +} + +// MarshalJSON adheres to the json.Marshaler interface. +func (rp RequestParams) MarshalJSON() ([]byte, error) { + pDisk := requestParamsDisk{ + Timeout: rp.Timeout, + MaxResponseMessages: rp.MaxResponseMessages, + CmixParams: rp.CmixParams, + } + + return json.Marshal(&pDisk) + +} + +// UnmarshalJSON adheres to the json.Unmarshaler interface. +func (rp *RequestParams) UnmarshalJSON(data []byte) error { + pDisk := requestParamsDisk{} + err := json.Unmarshal(data, &pDisk) + if err != nil { + return err + } + + *rp = RequestParams{ + Timeout: pDisk.Timeout, + MaxResponseMessages: pDisk.MaxResponseMessages, + CmixParams: pDisk.CmixParams, + } + + return nil +} diff --git a/single/params_test.go b/single/params_test.go index 39f56c80c888e1496dffebb2b9f60d8951cd055e..7d8224c6127366af542baecb2e755475311967a0 100644 --- a/single/params_test.go +++ b/single/params_test.go @@ -8,11 +8,42 @@ package single import ( + "bytes" + "encoding/json" "gitlab.com/elixxir/client/cmix" "reflect" "testing" ) +// Tests that no data is lost when marshaling and +// unmarshaling the RequestParams object. +func TestParams_MarshalUnmarshal(t *testing.T) { + p := GetDefaultRequestParams() + data, err := json.Marshal(p) + if err != nil { + t.Fatalf("Marshal error: %+v", err) + } + + t.Logf("%s", string(data)) + + received := RequestParams{} + err = json.Unmarshal(data, &received) + if err != nil { + t.Fatalf("Unmarshal error: %+v", err) + } + + data2, err := json.Marshal(received) + if err != nil { + t.Fatalf("Marshal error: %+v", err) + } + + t.Logf("%s", string(data2)) + + if !bytes.Equal(data, data2) { + t.Fatalf("Data was lost in marshal/unmarshal.") + } +} + // Tests that GetDefaultRequestParams returns a RequestParams with the expected // default values. func TestGetDefaultRequestParams(t *testing.T) {